/*
 * ﻿Copyright (C) 2012-2013 NewMain Softech
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
 * either express or implied. See the License for the specific language
 * governing permissions and limitations under the License.
 */
package com.newmainsoftech.ant.types;

import java.lang.reflect.Field;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;
import java.util.Arrays;
import java.util.Vector;

import org.apache.tools.ant.types.Commandline;
import org.apache.tools.ant.types.Commandline.Argument;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.newmainsoftech.aspectjutil.adviseutil.Util;

/**
 * Aspect to let {@link CommandlineExt#createArgument(boolean)} method return 
 * {@link ArgumentExt} object instead of {@link Argument} object. 
 * 
 * @author <a href="mailto:artymt@gmail.com">Arata Y.</a>
 */
@Aspect
public class CommandlineExtAspect {
	Logger logger = LoggerFactory.getLogger( this.getClass());
		protected Logger getLogger() {
			return logger;
		}

	static final Field argumentsField; 
		static {
			try {
				argumentsField = Commandline.class.getDeclaredField( "arguments");
				argumentsField.setAccessible( true);
			}
			catch( Exception exception) {
				if ( exception instanceof RuntimeException) throw (RuntimeException)exception;
				throw new RuntimeException(
						String.format(
								"Expected to find arguments member field in %1$s class but couldn't find. " 
								+ "%1$s class must have changed since %2$s has been developed.", 
								Commandline.class.getName(),
								CommandlineExtAspect.class.getName()
								),
						exception
						);
			}
		};
		static Field getArgumentsfield() {
			return argumentsField;
		}
		
	ArgumentExt genArgumentExt() {
    	Object argumentExtObj
    	= Proxy.newProxyInstance( 
        		ArgumentExt.class.getClassLoader(), 
        		new Class<?>[]{ ArgumentExtContract.class}, 
        		new ArgumentExt());
        return (ArgumentExt)argumentExtObj;
	}
		
	@Pointcut( 
		value="execution( public org.apache.tools.ant.types.Commandline.Argument " 
				+ "com.newmainsoftech.ant.types.CommandlineExt.createArgument( boolean)) " 
				+ "&& within( com.newmainsoftech.ant.types.CommandlineExt)")
	public static void pointcutAtExecutionOfCreateArgument() {}	
		/**
		 * {@link Around} advise method over {@link CommandlineExt#createArgument(boolean)} method 
		 * in order to return {@link ArgumentExt} object. 
		 * @param proceedingJoinPoint
		 * @return {@link ArgumentExt} object.
		 * @throws Throwable
		 */
		@Around( value="pointcutAtExecutionOfCreateArgument()") 
		public Object aroundAdvisedExecutionOfCreateArgument( ProceedingJoinPoint proceedingJoinPoint) 
		throws Throwable {
			Object targetObj = proceedingJoinPoint.getTarget();
			
			Logger logger = getLogger();
				if ( logger.isDebugEnabled()) {
					MethodSignature methodSignature = (MethodSignature)(proceedingJoinPoint.getSignature());
					logger.debug(
							String.format(
									"Entering %1$s method of %2$s.",
									methodSignature.getMethod().getName(), 
									targetObj.toString()
									)
							);
					logger.debug( 
							String.format(
									"Thead ID: %1$d%nStack-trace to here: %n%2$s",
									Thread.currentThread().getId(),
									Arrays.toString( Arrays.copyOf( Thread.currentThread().getStackTrace(), 10))
									)
							);
				}
				
			try {
				ArgumentExt argumentExt = new ArgumentExt();
				
				Commandline targetCommandline = (Commandline)targetObj;
				Vector argumentsMemberField = (Vector)(getArgumentsfield().get( targetCommandline));
				Boolean insertAtStart = (Boolean)(proceedingJoinPoint.getArgs()[0]);
				if ( insertAtStart.booleanValue()) {
					argumentsMemberField.insertElementAt( argumentExt, 0);
				}
				else {
					argumentsMemberField.addElement( argumentExt);
				}
				
				if ( logger.isDebugEnabled()) {
					logger.debug(
							String.format(
									"Returning %1$s instead of %2$s object.",
									argumentExt.toString(),
									Argument.class.getSimpleName()
									)
							);
					
					MethodSignature methodSignature = (MethodSignature)(proceedingJoinPoint.getSignature());
					logger.debug(
							String.format(
									"Exited %1$s method of %2$s",
									methodSignature.getMethod().getName(), 
									targetObj.toString()
									)
							);
				}
				
				return argumentExt;
			}
			catch( Throwable throwable) {
				if ( Util.isExceptionChecked( 
						(MethodSignature)(proceedingJoinPoint.getSignature()), 
						throwable)
						) 
				{
					throw throwable;
				}
				else {
					throw new UndeclaredThrowableException( throwable);
				}
			}
		}
/*		
	@Pointcut( value="cflowbelow( pointcutAtExecutionOfCreateArgument()) " 
			+ "&& within( org.apache.tools.ant.types.Commandline+)")
	public static void pointcutBelowExecutionOfCreateArgument() {}
	@Pointcut( value="call( public void java.util.Vector.insertElementAt(..)) " 
			+ "|| call( public void java.util.Vector.addElement(..))")
	public static void pointcutAtCallToVectorMethods() {};
	@Pointcut( value="pointcutAtCallToVectorMethods() " 
				+ "&& pointcutBelowExecutionOfCreateArgument()")
	public static void pointcutAtCallToVectorMethodsFromCreateArgument() {}
		@Around( value="pointcutAtCallToVectorMethodsFromCreateArgument()")
		public Object aroundAdvisedCallOfVectorMethodsFromCreateArgument( 
				ProceedingJoinPoint proceedingJoinPoint) throws Throwable 
		{
{
	Logger logger = getLogger();
	MethodSignature methodSignature = (MethodSignature)(proceedingJoinPoint.getSignature());
	logger.entering( methodSignature.getDeclaringTypeName(), methodSignature.getMethod().getName());
	logger.info( 
			String.format(
					"Thead ID: %1$d%nStack-trace to here: %n%2$s",
					Thread.currentThread().getId(),
					Arrays.toString( Arrays.copyOf( Thread.currentThread().getStackTrace(), 10))
					)
			);
	}
			try {
				AlternativeArgument alternativeArgument 
				= CommandlineExtAspect.getAlternativeArgumentInThreadLocal();
					if ( alternativeArgument == null) {
						throw new IllegalStateException(
								String.format(
										"Encountered the unexpected state that ThreadLocal does not hold " 
										+ "ArgumentExt object what should have already been instantiated by " 
										+ "aroundAdvisedExecutionOfCreateArgument advise. Suspect that " 
										+ "%1$s got code change.",
										Commandline.class.toString()
										)
								);
					}
				Object[] argObjArray = proceedingJoinPoint.getArgs();
					int counter = 0;
					StringBuffer stringBuffer = new StringBuffer();
					for( int index = 0; index < argObjArray.length; index++) {
						if ( !(argObjArray[ index] instanceof Argument)) continue;
						if ( argObjArray[ index] instanceof ArgumentExt) continue;
						
						if ( ++counter > 1) {
							throw new IllegalStateException(
									String.format(
											"Unexpected to find more than one Argument class object in " 
											+ "arguments of %1$s method invoked in %2$s method. " 
											+ "Suspect that %3$s got code change.",
											proceedingJoinPoint.getSignature().toShortString(),
											proceedingJoinPoint.getStaticPart().getSignature().toShortString(),
											Commandline.class.toString()
											)
									);
						}
						stringBuffer.append( 
								String.format(
										"%n%1$cswitched argument %2$d from %3$s to %4$s",
										'\t',
										index,
										argObjArray[ index].toString(),
										alternativeArgument.getArgumentExt().toString()
										)
								);
							
						argObjArray[ index] = alternativeArgument.getArgumentExt();
							alternativeArgument.setAccessed( true);
					} // for
						if ( stringBuffer.length() > 0) {
							Logger logger = getLogger();
							if ( logger.isLoggable( Level.FINE)) {
								logger.log(
										Level.FINE, 
										String.format( 
												"Among arguments being given to %1$s method of %2$s object, " 
												+ "Argument object was replaced to ArgumentExt object like " 
												+ "below before given to %1$s method: %3$s", 
												((MethodSignature)(proceedingJoinPoint.getSignature()))
													.getName(), 
												proceedingJoinPoint.getTarget().getClass().toString(),
												stringBuffer.toString()
												)
										);
							}
						}
{
	MethodSignature methodSignature = (MethodSignature)(proceedingJoinPoint.getSignature());
	logger.exiting( methodSignature.getDeclaringTypeName(), methodSignature.getMethod().getName());
}
						
				return proceedingJoinPoint.proceed( argObjArray);
			}
			catch( Throwable throwable) {
				if ( Util.isExceptionChecked( 
						(MethodSignature)(proceedingJoinPoint.getSignature()), 
						throwable)
						) 
				{
					throw throwable;
				}
				else {
					throw new UndeclaredThrowableException( throwable);
				}
			}
		}
*/
}
